class ProductImage

  @@photoresize_path  = "#{RAILS_ROOT}/vendor/plugins/PhotoResize/PR"


  # Maak een nieuw ProductImage-object aan. Deze klasse wordt normaal
  # alleen door de Product-klasse aangemaakt.
  def initialize(product)
    @product = product
  end


  # Geeft breedte en hoogte van deze afbeelding (zie examine_jpeg).
  def size
    self.examine_jpeg(ProductImage.image_path(filename))
  end


  # Heeft het opgegeven product een afbeelding en bestaat die afbeelding ook echt?
  def exists?
    !@product.image_src.blank? and File.readable?(ProductImage.image_path(@product.image_src))
  end


  # Retourneert de url naar de afbeelding of naar 'unknown.jpg' als het 
  # product geen afbeelding heeft.
  def src
    ProductImage.image_url(exists? ? @product.image_src : 'unknown.jpg')
  end


  # Resize deze afbeelding naar de opgegeven hoogte en breedte en retourneert
  # de url naar de geresizede afbeelding.
  def resize(width, height, crop=false)

    f = nil

    # Als het product een afbeelding heeft, resize die dan.
    if exists?
      f = ProductImage.resize_command(@product.image_src, width, height, crop)
    end

    # Als het resizen mislukt is, probeer dan unknown.jpg te resizen.
    if f == nil
      f = ProductImage.resize_command('unknown.jpg', width, height, crop)
    end

    # Als het mislukt is, retourneer dan unknown.jpg
    if f == nil
      f = 'unknown.jpg'
    end

    return ProductImage.image_url(f)
  end

  def save
    @product.save
  end


  # Upload een bestand van deze computer.
  def upload_file(file)

    # Vernietig de huidige afbeelding
    destroy

    # Find de extensie van de afbeelding (bijv. jpg of jpeg) en maak de nieuwe
    # naam aan (bijv. 5.jpg voor product met id 5)
    ext     = file.original_filename.split('.').last
    newname = @product.id.to_s + '.' + ext

    # Kopieer de inhoud van het gegeven bestand.
    FileUtils.copy(file.path, ProductImage.image_path(newname))

    # Wijzig de src van de afbeelding van dit product.
    @product.update_attribute(:image_src, newname)

    return true
  end


  # Upload een bestand van een andere website.
  def upload_url(url)

    # Vernietig de huidige afbeelding
    destroy

    # Find de extensie van de afbeelding (bijv. jpg of jpeg) en maak de nieuwe
    # naam aan (bijv. 5.jpg voor product met id 5)
    ext     = url.split('.').last
    newname = @product.id.to_s + '.' + ext


    # URI.parse kan een exception gooien, vang hem op voor de zekerheid.
    begin
      f  = File.new(ProductImage.image_path(newname), 'wb')


      uri = URI.parse(url)

      http = Net::HTTP.new(uri.host, uri.port)
      http.read_timeout = 5
      http.open_timeout = 5
      http.get(uri.path) do |body|
        f.write(body)
      end

    rescue URI::InvalidURIError, Timeout::Error
      return false
    ensure
      f.close unless f == nil
    end

    # Wijzig de src van de afbeelding van dit product.
    @product.update_attribute(:image_src, newname)

    return true
  end


  # Verwijder de afbeelding van het gegeven product en alle thumbnails die
  # gemaakt zijn.
  def destroy

    # Verwijder alleen de afbeelding als die nog bestaat.
    if exists?
      source = @product.image_src
      File.delete(ProductImage.image_path(source))

      dot = source.rindex('.')

      if dot != nil
        before_dot = source[0..(dot-1)]
        after_dot  = source[(dot+1)..(source.size-1)]

        
        # Alle thumbnails van bestand 'xxx.jpg' hebben het volgende formaat:
        # 'thumbnails/xxx-MWxH.jpg' waar M de modifier is, W de breedte en
        # H de hoogte. Door glob 'xxx*.jpg' uit te voeren worden alle
        # thumbnails voor dit product gevonden.
        Dir.glob(ProductImage.image_path("thumbnails/#{before_dot}*.#{after_dot}")) do |filename|
          File.delete(filename)
        end
      end

    end

    @product.update_attribute(:image_src, nil)
  end


  private
    # Geeft het absolute path naar een bestand in de images/products map.
    def self.image_path(file)
      RAILS_ROOT + '/public/images/products/' + file
    end

    # Geeft de absolute url naar een bestand in de images/products map.
    def self.image_url(file)
      '/images/products/' + file
    end

    def self.resize_command(filename, width, height, crop=false)

      # Find de positie van de laatste punt in de bestandsnaam/
      dot = filename.rindex('.')

      # Geen punt gevonden, dan is dit bestand zeker geen jpeg. Retourneer nil.
      if dot == nil
        return nil
      end

      # Find alles voor en na de punt
      # Bijv: "foo_bar.jpg"
      # - before_dot: "foo_bar"
      # - after_dot: "jpg"
      before_dot = filename[0..(dot-1)]
      after_dot  = filename[(dot+1)..(filename.size-1)]


      # Resize alleen jpeg- en jpg-bestanden. Andere bestanden worden niet
      # door PhotoResize geaccepteerd.
      unless ['jpeg', 'jpg'].include?(after_dot.downcase)
        return nil
      end


      # Zet de modifier:
      # - A: Het eindresultaat is altijd precies x pixels breed en y pixels hoog.
      # - F: De afbeelding wordt geschaald zodat de breedte maximaal x pixels
      #      of de hoogte y pixels is.
      modifier = crop ? 'A' : 'F'


      # Maak de naam van het nieuwe bestand aan.
      resize_filename = "thumbnails/#{before_dot}-#{modifier}#{width}x#{height}.#{after_dot}"
      resize_path     = self.image_path(resize_filename)


      # Als de thumbnail al bestaat is niet nodig is het niet nodig om nog
      # een keer de resize te maken.
      if File.readable?(resize_path)
        return resize_filename
      end


      w = width
      h = height

      if !crop and data = self.examine_jpeg(filename)
        scale = [
          1,
          width.to_f/data[:width].to_f,
          height.to_f/data[:height].to_f
        ].min

        w = (data[:width]*scale).to_i
        h = (data[:height]*scale).to_i
      end

      #
      f = IO.popen("#{@@photoresize_path} -o -q90 -c#{resize_path} -#{modifier}#{w}x#{h} " + self.image_path(filename))


      # Het is belangrijk om de stream te sluiten omdat het procces anders
      # altijd op de achtergrond blijft bestaan.
      f.close if f

      # Retourneer nil
      return nil
    end



    # Deze code komt van http://snippets.dzone.com/posts/show/805
    # Retourneert informatie over het opgegeven jpeg-bestand, bijvoorbeeld:
    # {width: 500, height: 100, bits: 32}
    # Retourneert nil als het bestand geen geldig jpeg-bestand is.
    def self.examine_jpeg(file)

      io = File.open(self.image_path(file), 'rb')  do |io|

        return nil unless io.getc == 0xFF && io.getc == 0xD8 # SOI

        class << io
          def readint; (readchar << 8) + readchar; end
          def readframe; read(readint - 2); end
          
          #             length,  bits,     height,  width,   components
          def readsof; [readint, readchar, readint, readint, readchar]; end
          def next
            c = readchar while c != 0xFF
            c = readchar while c == 0xFF
            c
          end
        end

        while marker = io.next
          case marker
            when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF # SOF markers
              sof = io.readsof

              #if length == 8 + 3 * components
              if  sof[0] == 8 + 3 * sof[4]
                return {
                  :bits   => sof[1],
                  :height => sof[2],
                  :width  => sof[3]
                }
              else
                return nil
              end
            when 0xD9, 0xDA:  return nil # EOI, SOS
            else              io.readframe # ignore frame
          end
        end
      end
    end


end